package wator.model;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * An instance of this class encapsulates the set of neighbors for an agent.
 * More practical, this is a set of agents and their relative positions to a
 * point in space. Neighborhoods may contain agents from multiple species.
 * 
 * @see Agent
 */
public class Neighborhood 
		implements Iterable<wator.model.Neighborhood.Neighbor> {

	private List<Neighbor> elements;
	private IntegerVector pos;
	private Topology topo;
	private int radius2;
	
	/**
	 * This class represents an entry in the neighborhood. This means the
	 * data pair of the agent reference itself and its the relative position. 
	 */
	public class Neighbor {
		private IntegerVector d;
		private Agent agent;
		
		/* (non-Javadoc)
		 * Initializes this neighbor with given distance vector and agent
		 * reference.
		 */
		private Neighbor(IntegerVector d, Agent agent) {
			this.d = d;
			this.agent = agent;
		}
		
		/**
		 * Get the distance vector. This is the relative position from the
		 * position vector <code>pos</code>. The length of this vector also
		 * defines the distance of the neighbor.
		 * 
		 * @return the distance vector
		 */
		public IntegerVector getDistanceVector() {
			return d;
		}
		
		/**
		 * Get the agent reference.
		 * 
		 * @return the agent
		 */
		public Agent getAgent() {
			return agent;
		}
	}
	
	/**
	 * Sole constructor. Creates an empty neighborhood.
	 * 
	 * @param topo the topology which should be used to determine the distance
	 * 			vector
	 * @param pos position vector of the neighborhood center
	 * @param radius2 quadratic radius of the hyper-ball defining the 
	 * 			neighborhood
	 * @throws NullPointerException if <code>pos</code> or <code>topo</code> 
	 * 			is null
	 */
	public Neighborhood(Topology topo, IntegerVector pos, int radius2) {
		if (pos == null || topo == null) {
			throw new NullPointerException();
		}
		this.topo = topo;
		this.pos = pos;
		this.radius2 = radius2;
		elements = new LinkedList<Neighbor>();
	}
	
	/**
	 * Adds a new agent to the neighborhood if its position is within the
	 * hyper-ball.
	 * 
	 * @param agent the agent to add
	 * @return true if the agent was added, false otherwise
	 * @throws NullPointerException if <code>agent</code> is null
	 */
	public boolean addAgent(Agent agent) {
		IntegerVector d = topo.distanceVector(pos, agent.getPositionVector());
		if (d.length2() <= radius2) {
			elements.add(new Neighbor(d, agent));
			return true;
		}
		return false;
	}
	
	/**
	 * Set a new radius for this neighborhood. All agents with a greater
	 * distance are deleted from the set.
	 * 
	 * @param radius2 the radius power 2
	 */
	public void setRadius2(int radius2) {
		if (radius2 < this.radius2) {
			LinkedList<Neighbor> newElements = new LinkedList<Neighbor>();
			for (Neighbor n : elements) {
				if (n.getDistanceVector().length2() <= radius2) {
					newElements.add(n);
				}
			}
			elements = newElements;
		}
		this.radius2 = radius2;
	}

	/* (non-Javadoc)
	 * @see java.lang.Iterable#iterator()
	 */
	@Override
	public Iterator<Neighbor> iterator() {
		return elements.iterator();
	}
}
